/*
 * loadconfig.c
 *
 *  Created on: Sep 1, 2010
 *      Author: o2o
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include "log.h"
#include "config.h"

#define IO_ERROR 0
#define IO_SUCCESS 1

#define NEWLINE_CHAR '\n'
#define TAB_CHAR '\t'
#define BLANK_CHAR ' '
#define NUMBER_SIGN_CHAR '#'
#define EQUALS_SIGN_CHAR '='
#define DEFAULT_SERVER_NAME "filecache"
#define DEFAULT_ALLOCATE_VALUE 32

#ifndef DEFAULT_ARRAY_SIZE
#define DEFAULT_ARRAY_SIZE 32
#endif

/* Thomas Wang's 32 bit Mix Function */
#undef HASH
#define HASH(key) do {		\
	key += ~(key << 15);	\
	key ^= (key >> 10);		\
	key += (key << 3);		\
	key ^= (key >> 6);		\
	key += ~(key << 11);	\
	key ^= (key >> 16);		\
}while (0)					\

typedef struct _config_entry {
	struct _config_entry *next;
	char *key;
	char *value;
	unsigned int hashcode;
} config_entry;

typedef struct hashtable {
	config_entry **table;
	unsigned int size;
} hashtable;

/****** FUNCTION PROTOTYPE ******/
static int openInputStream(char *fileName);
static void closeInputStream();

static void getConfigLine(char **outputkey, char **outputvalue);
static int getAllProperties(hashtable *configTab);
static int equals(char *src, char *dest);

static hashtable* hashtab_init();
static int hashtab_make_index(unsigned int hashcode, int tablen);
static unsigned int make_hashcode(char *str);
static void hashtab_put(hashtable* hashtab, config_entry* entry);
static char* hashtab_get(hashtable *hashtab, char *key);
static config_entry* config_entry_makenew();
static void drop_config_entries(config_entry* list);
static void hashtab_drop(hashtable *hashtab);

ServerConf *allocateConfigProperties();
ServerConf* load_config();

/***** GLOBAL VARIABLE ******/
static FILE *inputStream;
static hashtable *configTable;

/**
 * Open input stream.
 * @param: name of file.
 * @return: IO_SUCCESS if open successfully and IO_ERROR if failure.
 */
static int openInputStream(char *fileName) {
	inputStream = fopen(fileName, "rt");
	if (inputStream == NULL)
		return IO_ERROR;

	return IO_SUCCESS;
}

/**
 * Close input stream.
 */
static void closeInputStream() {
	fclose(inputStream);
}

/**
 * scale size of string array.
 */
static void scale_string_array(char **input_ptr, int newsize) {
	char *str = *input_ptr;

	*input_ptr = (char*) malloc(newsize * sizeof(char));
	//	memset(*input_ptr, '\0', newsize*sizeof(char));
	memcpy(*input_ptr, str, sizeof(str));

	free(str);
}

/**
 * Get an config line from file.
 * @param1: output key that read from config file.
 * @param2: output value.
 */
static void getConfigLine(char **outputkey, char **outputvalue) {
	char *line = NULL;
	size_t size = 128;
	ssize_t nread = 0;

	nread = getline(&line, &size, inputStream);
	//nread = getdelim(&line, &size, '\n', inputStream);
	if (nread <= 0 || !line) {
		if (line)
			free(line);
		return;
	}

	int key_flag = 1, value_flag = 0;
	int counter = 0;

	char *linePtr = line;
	for (; *linePtr != '\0'; linePtr++) {
		switch (*linePtr) {

		case NUMBER_SIGN_CHAR:
			free(line);
			return; /* skip comment line*/
		case BLANK_CHAR:
		case TAB_CHAR:
		case NEWLINE_CHAR: /* skip blank character*/
			break;
		case EQUALS_SIGN_CHAR:
			key_flag = 0;
			value_flag = 1;
			(*outputkey)[counter] = '\0';
			counter = 0;
			break;
		default:
			if (key_flag) {
				if (!(*outputkey)) {
					*outputkey = (char*) malloc(DEFAULT_ALLOCATE_VALUE
							* sizeof(char));
				}
				if (counter >= DEFAULT_ALLOCATE_VALUE) {
					scale_string_array(outputkey, 2 * DEFAULT_ALLOCATE_VALUE);
				}
				(*outputkey)[counter] = *linePtr;
				counter++;
			}

			if (value_flag) {
				if (!(*outputvalue)) {
					*outputvalue = (char*) malloc(DEFAULT_ALLOCATE_VALUE
							* sizeof(char));
				}
				if (counter >= DEFAULT_ALLOCATE_VALUE) {
					scale_string_array(outputvalue, 2 * DEFAULT_ALLOCATE_VALUE);
				}
				(*outputvalue)[counter] = *linePtr;
				counter++;
			}
			break;
		}
	}

	if ((*outputvalue))
		(*outputvalue)[counter] = '\0';

	free(line);
}

/**
 * Load all properties into a hash table.
 * @warning: initialize for hash table before call this function
 * @return: The number of properties.
 */
static int getAllProperties(hashtable *configTab) {
	int count = 0;

	config_entry *entry;
	while (!feof(inputStream)) {
		entry = config_entry_makenew();
		getConfigLine(&entry->key, &entry->value);
		if (entry->key) {
			entry->hashcode = make_hashcode(entry->key);
			// Put to hash table.
			hashtab_put(configTab, entry);
			count++;
		} else {
			free(entry);
		}
	}

	return count;
}

/**
 * GET CONFIG
 */
#ifndef DEFAULT_PORT
#define DEFAULT_PORT 6379
#endif

#define HTTP_TIME_OUT 115			/* Default HTTP_TIME_OUT     */
#define HTTP_THREAD_NUMBER 1		/* Default HTTP_THREAD_NUMBER*/
#define HTTP_PENDDING 128			/* Default HTTP_PENDDING	 */
#define HTTP_DEFAULT_PORT 8080		/* Default HTTP_DEFAULT_PORT */
#define HTTP_FILE_CHECK_TIME 60
/**
 * Allocate a record that store all config of webqueue server.
 * @return: A pointer that refer to allocated memmory block.
 */
ServerConf *allocateConfigProperties() {
	ServerConf *sc = (ServerConf*) malloc(sizeof(ServerConf));
	if (sc == NULL) {
		fprintf_log(LOG_ERROR, "[%s] In %s() %d: malloc failed!", __FILE__,
				__PRETTY_FUNCTION__, __LINE__);
		exit(EXIT_FAILURE);
	}
	memset(sc, 0, sizeof(ServerConf));

	sc->httpTimeout = HTTP_TIME_OUT;
	sc->httpPort = HTTP_DEFAULT_PORT;
	sc->httpPending = HTTP_PENDDING;
	sc->httpThreadNum = HTTP_THREAD_NUMBER;

	// Initialize MIME's configuration hash table.
	sc->httpMimeTypes = hashtab_init();
	return sc;
}

/**
 * Compare 2 strings.
 * @param 1: source string
 * @param 2: destination string
 * @return: 1 if is equal or both are NULL) and 0 in otherwise.
 */
static int equals(char *src, char *dest) {
	if (src != NULL && dest != NULL) {
		return strcmp(src, dest) == 0 ? 1 : 0;
	} else if (src == NULL && dest == NULL) {
		return 1;
	}

	return 0;
}

int ceilbase2(int value) {
	int counter = 1;
	while ((value = value >> 1) != 0) {
		++counter;
	}
	return 1 << counter;
}

/**
 * Load all config for webqueue server.
 * @return: A ServerConf pointer that refer to a record has config information.
 */
ServerConf* load_config() {
	ServerConf *servConf = allocateConfigProperties();

	if (openInputStream(FILE_CONFIG) == IO_ERROR) {
		fprintf_log(LOG_ERROR, "[%s] In %s() %d: Open config file failed!",
				__FILE__, __FUNCTION__, __LINE__);
		return NULL;
	}

	// Initialize configuration's hash table.
	configTable = hashtab_init();
	getAllProperties(configTable);
	int port = 0;
	char *temp;

	// HTTP server config part.
	if ((temp = hashtab_get(configTable, "http.timeout")) != NULL) {
		servConf->httpTimeout = atoi(temp);
	}
	if ((temp = hashtab_get(configTable, "http.threadNum")) != NULL) {
		servConf->httpThreadNum = atoi(temp);
	}
	if ((temp = hashtab_get(configTable, "http.port")) != NULL) {
		port = atoi(temp);
		if (0 <= port && port <= 65535) {
			servConf->httpPort = port;
		}
	}
	if ((temp = hashtab_get(configTable, "http.pendding")) != NULL) {
		servConf->httpPending = atoi(temp);
	}

	servConf->httpWebRoot = hashtab_get(configTable, "http.webroot");
	if (servConf->httpWebRoot == NULL) {
		fprintf_log(LOG_ERROR,
				"[%s] In %s() %d: webroot = NULL, please specify a location",
				__FILE__, __FUNCTION__, __LINE__);
	}

	servConf->httpServerName = hashtab_get(configTable, "http.servername");
	if (servConf->httpServerName == NULL) {
		servConf->httpServerName = DEFAULT_SERVER_NAME;
	}
	if ((temp = hashtab_get(configTable, "cache.size")) != NULL) {
		servConf->cacheSize = atoi(temp);
	}

	if ((temp = hashtab_get(configTable, "cache.lock")) != NULL) {
		servConf->cacheLock = ceilbase2(atoi(temp));
	}

	// Log config part.
	servConf->log_level = hashtab_get(configTable, "log.level");
	servConf->log_file = hashtab_get(configTable, "log.file");
	if ((temp = hashtab_get(configTable, "log.maxbackup")) != NULL) {
		servConf->log_maxbackup = atoi(temp);
	}
	if ((temp = hashtab_get(configTable, "log.maxbytes")) != NULL) {
		servConf->log_maxbytes = atoi(temp);
	}

	// if free configTable then need duplicate all value above using strdup (check null before call strdup)
	//hashtab_drop(configTable);
	closeInputStream();

	// Read MIME's configuration.
	if (openInputStream(MIME_FILE_CONFIG) == IO_ERROR) {
		fprintf_log(LOG_ERROR, "[%s] In %s() %d: Open config file failed!",
				__FILE__, __FUNCTION__, __LINE__);
		return NULL;
	}

	getAllProperties(servConf->httpMimeTypes);

	closeInputStream();

	return servConf;
}

/*--------------------------------------------------------------------------------------------------------------------------------------*/
char* getHeaderByMimeType(struct hashtable *htab, char *mtype) {
	return hashtab_get(htab, mtype);
}

/*--------------------------------------------------------------------------------------------------------------------------------------*/

/*** DECLARE UTILLITIES FOR HASH TABLE ****/

static hashtable* hashtab_init() {
	hashtable* hashtab = (hashtable *) malloc(sizeof(hashtable));
	if (!hashtab) {
		fprintf_log(LOG_ERROR, "[%s] In %s() %d: calloc() failed!", __FILE__,
				__FUNCTION__, __LINE__);
		exit(EXIT_FAILURE);
	}

	hashtab->table = calloc(DEFAULT_ARRAY_SIZE, sizeof(config_entry*));
	if (hashtab->table == NULL) {
		fprintf_log(LOG_ERROR, "[%s] In %s() %d: calloc() failed!", __FILE__,
				__FUNCTION__, __LINE__);
		exit(EXIT_FAILURE);
	}

	hashtab->size = 0;

	return hashtab;
}

static int hashtab_make_index(unsigned int hashcode, int tablen) {
	return hashcode & (tablen - 1);
}

static unsigned int make_hashcode(char *str) {
	unsigned int hashcode = 0;

	for (; *str != '\0'; str++) {
		hashcode = 31 * hashcode + *str;
	}
	HASH(hashcode);

	return hashcode;

}

//
static void hashtab_put(hashtable* hashtab, config_entry* entry) {
	if (!entry)
		return;

	int index = hashtab_make_index(entry->hashcode, DEFAULT_ARRAY_SIZE);
	entry->next = hashtab->table[index];
	hashtab->table[index] = entry;

	hashtab->size++;
}

static char* hashtab_get(hashtable *hashtab, char *key) {
	unsigned int hashcode = make_hashcode(key);
	int index = hashtab_make_index(hashcode, DEFAULT_ARRAY_SIZE);

	config_entry *en = hashtab->table[index];
	while (en != NULL) {
		if (en->hashcode == hashcode && equals(key, en->key)) {
			return en->value;
		}

		en = en->next;
	}

	return NULL;
}

static config_entry* config_entry_makenew() {
	config_entry *entry = (config_entry*) malloc(sizeof(config_entry));
	memset(entry, 0, sizeof(config_entry));

	return entry;
}

static void drop_config_entries(config_entry* list) {
	config_entry *en;
	if (list == NULL)
		return;
	do {
		en = list;
		list = en->next;

		// free entry
		free(en->key);
		free(en->value);
		free(en);
	} while (list != NULL);
}

static void hashtab_drop(hashtable *hashtab) {
	int i;
	for (i = 0; i < DEFAULT_ARRAY_SIZE; i++) {
		drop_config_entries(hashtab->table[i]);
	}

	free(hashtab->table);
	free(hashtab);
}
